﻿// This file is part of WifiCC3000 - the Managed Wifi Driver for the TI CC3000
// Hosted on Codeplex: http://cc3000.codeplex.com
// WifiCC3000 is free software licensed under the Apache License 2.0
// © Copyright 2012 ValkyrieTech, LLC
// by Valkyrie-MT and Ziggurat29

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using System.Threading;
using System.Text;

namespace Networking
{
    public class WifiAdapter : IDisposable
    {
        public event ConnectedEventHandler Connected;
        public delegate void ConnectedEventHandler(WifiAdapter wifiAdapter);

        //UdpSocket llmnr = null;
        UdpSocket mdns = null;
        UdpSocket nbns = null;

        private TimeSpan LastNameResponse = TimeSpan.MaxValue;
        private TimeSpan LastNetbiosProbe = TimeSpan.MaxValue;

        private Timer bouncer;
        private TimeSpan bounceFrequency = new TimeSpan(0, 6, 0, 0);  // This is the automated CC3000 reboot frequency.  

        TimeSpan lastReset = PowerState.Uptime;

        /// <summary>
        /// Start Networking!    
        /// </summary>
        /// <param name="name">Local Name of this device</param>
        /// <param name="spiBus"></param>
        /// <param name="resetPin"></param>
        /// <param name="interruptPin"></param>
        /// <param name="chipSelectPin"></param>
        public WifiAdapter(string name, SPI.SPI_module spiBus, Cpu.Pin interruptPin, Cpu.Pin chipSelectPin, Cpu.Pin enablePin, bool ExceptionOnStartFailure = true, bool ConnectToStoredProfile = true)
        {
            InitAdapter(name, spiBus, interruptPin, chipSelectPin, enablePin, ExceptionOnStartFailure, ConnectToStoredProfile);
        }

        private void InitAdapter(string name, SPI.SPI_module spiBus, Cpu.Pin interruptPin, Cpu.Pin chipSelectPin, Cpu.Pin enablePin, bool ExceptionOnStartFailure = true, bool Connect = true)
        {
            if (wifi != null && wifi is CC3000Driver) return;

            wifi = new CC3000Driver(interruptPin, chipSelectPin, enablePin, spiBus);

            this.Name = name;

            wifi.OnConnectedEvent += wifi_OnConnectedEvent;
            wifi.Start(0, ExceptionOnStartFailure, Connect);

            bouncer = new Timer(new TimerCallback(BounceNow), null, bounceFrequency, bounceFrequency);
        }

        private void BounceNow(object state)
        {
            // && this.IsConnectedToWifi
            if (!this.IsResetting && PowerState.Uptime.Subtract(lastReset) > bounceFrequency)
            {
                Debug.Print("Periodic Reset!");

                this.Reset();
                lastReset = PowerState.Uptime;
            }
        }

        /// <summary>
        /// Start Networking!   
        /// </summary>
        /// <param name="name">This is the name used for local name resolution services.  Setting the local name to null will disable Local Name Resolution</param>
        /// <param name="profile">This is the Hardware interface you are using to connect the CC3000 to the Host controller</param>
        public WifiAdapter(string name = "CC3000", InterfaceProfile profile = InterfaceProfile.NetduinoPlus2_CC3000, bool ExceptionOnStartFailure = true, bool ConnectToStoredProfile = true)
        {
            // Setup connection from Profile
            switch (profile)
            {
                // On NetduinoPlus2:
                // D4 is 28
                // D5 is 24
                // D6 is 25

                // A CC3000 connected to Netduino2Plus on MOSI(D11), SCK(D13), MISO(D12), INT(D6), CS(D4), PWREN(D5)
                case InterfaceProfile.NetduinoPlus2_CC3000:
                    // MOSI(PA7), SCK(PA5), MISO(PB4), INT(PB9), CS(PC8) 
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)25, (Cpu.Pin)28, (Cpu.Pin)24, ExceptionOnStartFailure, ConnectToStoredProfile);
                    break;
                case InterfaceProfile.CerbuinoBee_Adhoc_CC3000:
                    // INT(PA10=D6=10), CS(PC15=D4=47), PWREN(PA8=D5=8), MOSI(PB5=D11=21), MISO(PB4=D12=20), SCK(PB3=D13=19) for CerbuinoBee Adhoc
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)10, (Cpu.Pin)47, (Cpu.Pin)8, ExceptionOnStartFailure, ConnectToStoredProfile);
                    break;
                //TODO: Eventually, we'll want to allow any socket on the NetduinoGo to work.  
                case InterfaceProfile.NetduinoGo_Socket3_CC3000:
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)34, (Cpu.Pin)4, (Cpu.Pin)4, ExceptionOnStartFailure, ConnectToStoredProfile);
                    break;
                case InterfaceProfile.Cerberus_Socket6_CC3000:
                case InterfaceProfile.CerbuinoBee_Socket1_CC3000:
                    // MOSI(PB5), SCK(PB3), MISO(PB4), INT(PA14), CS(PA13), PWR_EN(PB10) for CERB40 connection  (RESET(PC1) was removed because it is unnecessary)
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)0x0E, (Cpu.Pin)0x0D, (Cpu.Pin)0x1A, ExceptionOnStartFailure, ConnectToStoredProfile);
                    break;
                case InterfaceProfile.VTech_Device:
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)0x26, (Cpu.Pin)0x06, (Cpu.Pin)0x1B, ExceptionOnStartFailure, ConnectToStoredProfile); 
                    break;
                case InterfaceProfile.VTech_Device2:
                    InitAdapter(name, SPI.SPI_module.SPI1, (Cpu.Pin)0x0A, (Cpu.Pin)0x06, (Cpu.Pin)0x1B, ExceptionOnStartFailure, ConnectToStoredProfile); 
                    break;
                default:
                    InitAdapter(name, SPI.SPI_module.SPI2, (Cpu.Pin)14, (Cpu.Pin)13, (Cpu.Pin)15, ExceptionOnStartFailure, ConnectToStoredProfile);
                    break;
            }
        }

        void wifi_OnConnectedEvent(WifiAdapter sender)
        {
            if (this.Connected != null)
            {
                if (Name.IsNullOrEmpty() == false && IsLocalNameServicesStarted == false)
                {
                    if (wifi.Name != null && wifi.Name.Trim() != string.Empty)
                    {
                        // Listen for MDNS packets
                        if (mdns == null)  // check for null so we don't open new sockets on a reconnect
                        {
                            mdns = this.OpenUdpSocket();
                            mdns.PollFrequency = 400;
                            mdns.OnDataReceived += mdns_OnDataReceived;
                            mdns.Listen(5353, new byte[] { 224, 0, 0, 251 });  // MDNS
                        }

                        // Listen for NetBios Name requests
                        if (nbns == null)  // check for null so we don't open new sockets on a reconnect
                        {
                            nbns = this.OpenUdpSocket();
                            nbns.PollFrequency = 400;
                            nbns.OnDataReceived += nbns_OnDataReceived;
                            nbns.Listen(137);  // Netbios Port
                        }
                    }

                    IsLocalNameServicesStarted = true;
                }
                
                this.Connected.Invoke(this);            
            }
        }

        void nbns_OnDataReceived(UdpSocket sender, byte[] message, byte[] remoteIP, ushort remotePort)
        {
            Debugger.VerbosePrint("NetBIOS Message Detected from IP: " + remoteIP.ToAddress());

            var nameRequested = message.ParseNameFromNbnsQuery(wifi.Name);

            if (!remoteIP.BytesEqual(this.IPAddress) && remotePort == 137 && (nameRequested != null || message == null))
            {
                byte[] response = NetBiosNaming.ConstructNetbiosResponse(nameRequested, wifi.IPAddress, (message == null ? new byte[2] : message.ParseTransactionID()));
                Debugger.VerbosePrint("Sending NetBIOS Response!  " + response.ToHexString());
                //sender.Send(response, this.IPAddress.GetBroadcastAddress(this.SubnetMask), 137);  // broadcast the name
                sender.Send(response, remoteIP, 137);  // Directly address the name to the requestor
                LastNetbiosProbe = PowerState.Uptime;
            }
            else if (remoteIP.BytesEqual(this.IPAddress) && remotePort == 137)
            {
                LastNameResponse = PowerState.Uptime;
            }
        }

        void mdns_OnDataReceived(UdpSocket sender, byte[] message, byte[] remoteIP, ushort remotePort)
        {
            if (remoteIP.BytesEqual(this.IPAddress)) return;  // do not respond to our own MDNS messages!  

            Debugger.VerbosePrint("Multicast MDNS UDP Message Received (" + message.Length + " bytes) from " + remoteIP.ToAddress() + ":" + remotePort);

            var name = message.ParseNameFromDnsQuery(this.Name);

            if (name != null)
            {
                this.SendDeviceAdvertisement(name);
            }
        }

        /// <summary>
        /// If the cc3000 is Disconnected from the access point (for whatever reason), should the cc3000 persistently try to reconnect? 
        /// True for yes, False for no. 
        /// Default is True.  
        /// </summary>
        public bool StayConnected
        {
            get { return this.wifi.ShouldReconnect; }
            set { this.wifi.ShouldReconnect = value; }
        }

        /// <summary>
        /// Warning, this read/write test may corrupt your MAC address if it does not finish!  Use at your own risk!!!
        /// Save your MAC address before trying!  
        /// </summary>
        /// <returns></returns>
        public bool ReadWriteTest()
        {

            // Restart wlan with the (2) parameter to enable patching
            wifi.Start(1);  // mode 2 based on this: http://e2e.ti.com/support/low_power_rf/f/851/t/274780.aspx?pi267162=6

            var savedMac = wifi.NonVolatileMemoryRead(Constants.NVMEM_MAC_FILEID, 0, 6);

            if (savedMac == null || savedMac.Length < 6)
            {
                Debugger.Print("Unable to read user content, exiting! ");

                // reboot in mode (0), nothing altered
                wifi.Shutdown();
                wifi.Start();

                return false; // failed to get the Mac address!
            }

            Debugger.Print("MAC Address before write: " + savedMac.ToHexString());
            byte[] userContent = null;

            Debugger.Print("Writing 010203040506 to NV RAM");

            wifi.WriteEnabled = true;
            var resultInt = wifi.NvMemWriteFile(Constants.NVMEM_MAC_FILEID, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
            Debugger.Print("Result Int: " + resultInt);

            //var result = wifi.NonVolatileMemoryWrite(Constants.NVMEM_MAC_FILEID, 0, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
            wifi.WriteEnabled = false;

            userContent = wifi.NonVolatileMemoryRead(Constants.NVMEM_MAC_FILEID, 0, 6);

            if (userContent == null || userContent.Length < 6)
            {
                Debugger.Print("Unable to read user content, exiting! ");

                // reboot in mode (0), nothing altered
                wifi.Shutdown();
                wifi.Start();

                return false; // failed to get the Mac address!
            }

            Debugger.Print("Content of NV RAM after write: " + userContent.ToHexString());

            Debugger.Print("... Restoring original value: " + savedMac.ToHexString());

            wifi.WriteEnabled = true;
            wifi.NonVolatileMemoryWrite(Constants.NVMEM_MAC_FILEID, 0, savedMac);
            wifi.WriteEnabled = false;

            // reboot in mode (0)
            wifi.Shutdown();
            wifi.Start();

            return true;
        }

        public bool PatchFirmware(byte MajorSpVersion, byte MinorSpVersion, byte[] firmware, byte[] driver, byte[] fileAllocationTable = null)
        {
            // Useful docs:
            // http://processors.wiki.ti.com/index.php/CC3000_Flashing_Guide
            // http://processors.wiki.ti.com/index.php/CC3000_Release_Notes
            // http://processors.wiki.ti.com/index.php/CC3000_EEPROM_user_interface
            // Good description of the files in the cc3000 and their purpose: http://processors.wiki.ti.com/index.php/CC3000_Host_Programming_Guide#CC3000_NVMEM_API.E2.80.99s

            if (firmware == null || driver == null) throw new ArgumentNullException();

            // it has been observed that using an SPI speed too slow can cause the Simple Link Start to Time out
            // http://e2e.ti.com/support/low_power_rf/f/851/t/274780.aspx?pi267162=6
            if (wifi.spi.Config.Clock_RateKHz < 1000) throw new Exception("SPI Clock speed is set too slow for firmware update.");

            // Check Firmware Version.  This is actually important because if a device is programmed with this Patch programmer and the check is disabled, everytime the device restarts, it will start patching again...  And if someone does not realize this it could be interrupted during flashing.  
            if (wifi.FirmwareVersion != null && (new byte[] { wifi.FirmwareVersion[3], wifi.FirmwareVersion[2] }.ToShort()) >= (new byte[] { MinorSpVersion, MajorSpVersion }.ToShort()))
            {
                throw new Exception("Firmware is already up to date.");
            }

            wifi.WriteEnabled = true;  // This needs to be set to true when actually writing!

            int result = -1;
            byte[] response;

            // Reboot cc3000
            wifi.PowerUp();

            // Restart wlan with the (2) parameter to enable patching
            //wifi.Start(1);  // mode 2 based on this: http://e2e.ti.com/support/low_power_rf/f/851/t/274780.aspx?pi267162=6
            Debugger.Print("Sending - Simple Link Start (2)");
            response = wifi.Send(Command.Builder(HciCommand.HCI_CMND_SIMPLE_LINK_START, (byte)2), 5000);

            if (response == null || response.Length != 5 || response[4] != 0x00)
            {
                Debugger.Print("Invalid Simple Link Response, Trying Mode 1");
                // Reboot cc3000
                wifi.PowerUp();
                
                response = wifi.Send(Command.Builder(HciCommand.HCI_CMND_SIMPLE_LINK_START, (byte)1), 5000);

                if (response == null || response.Length != 5 || response[4] != 0x00)
                {
                    Debugger.Print("Invalid Simple Link Response, Aborting!");
                    wifi.Shutdown();
                    return false;
                }
            }


            var savedMacAddress = wifi.NonVolatileMemoryRead(Constants.NVMEM_MAC_FILEID, 0, 6);

            if (savedMacAddress == null || savedMacAddress.Length != 6 || savedMacAddress[0] != 0x08)
            {
                Debugger.Print("Unable to save MAC Address, exiting! Firmware was NOT updated.  ");

                // reboot in mode (0), nothing altered
                wifi.Start();

                return false; // failed to get the Mac address!
            }

            var savedRadioStuff = wifi.NonVolatileMemoryRead(Constants.NVMEM_RM_FILEID, 0, 128);

            if (savedRadioStuff == null || savedRadioStuff.Length != 128)
            {
                Debugger.Print("Unable to save Radio info, exiting!  Firmware was NOT updated.  ");
                
                // reboot in mode (0)
                wifi.Start();

                return false; // failed to get the radio parameters!
            }

            var fat = wifi.NonVolatileMemoryRead(Constants.NVMEM_MAX_ENTRY, 0, 68);

            Debugger.Print("FAT: " + fat.ToHexString());

            Debugger.Print("Your MAC address is: " + savedMacAddress.ToHexString() + "\nWRITE IT DOWN!!!!!!!!!!!!!!!!!!  \nIf this patch fails, you may need to restore it manually!  \nIf you lose your MAC address, it is gone forever!");
            Debugger.Print("Radio Stuff: " + savedRadioStuff.ToHexString());

            //TODO: backup more stuff here before wiping, including the user area?

            Debugger.Print("\n****  PATCHING FIRMWARE NOW *****   -- PLEASE WAIT!");

            wifi.spVersion = null;  

            int tries = 7;

            if (fileAllocationTable != null)
            {
                Debugger.Print("Writing new File Allocation Table Now. ");

                result = wifi.NonVolatileMemoryWrite(Constants.NVMEM_MAX_ENTRY, 0, new byte[] { 0x4C, 0x53 });

                result = wifi.NvMemWriteFile(Constants.NVMEM_MAX_ENTRY, fileAllocationTable, 4);

                // restart to enable the new FAT
                wifi.PowerUp();

                if (result != 0)
                {
                    Debugger.Print("Failed to Write File Allocation Table! ");
                    return false;
                }

                // Restart wlan with the (1) parameter to enable patching
                // use mode 2 based on this?: http://e2e.ti.com/support/low_power_rf/f/851/t/274780.aspx?pi267162=6
                Debugger.Print("Sending - Simple Link Start");
                response = wifi.Send(Command.Builder(HciCommand.HCI_CMND_SIMPLE_LINK_START, (byte)2), 5000);

                if (response == null || response[4] != 0)
                {
                    Debugger.Print("Startup Failed. ");
                    return false;
                }

                // Write Radio File
                while (tries-- > 0 && result != 0)
                    result = wifi.NvMemWriteFile(Constants.NVMEM_RM_FILEID, savedRadioStuff);

                result = -1;

                // Write Mac Address
                while (tries-- > 0 && result != 0)
                    result = wifi.NvMemWriteFile(Constants.NVMEM_MAC_FILEID, savedMacAddress);

                result = -1;
            }

            result = -1;

            // Write Driver
            while (tries-- > 0 && result != 0)
                result = wifi.NvMemWriteFile(Constants.NVMEM_WLAN_DRIVER_SP_FILEID, driver);

            result = -1;

            // Write Firmware
            while (tries-- > 0 && result != 0)
                result = wifi.NvMemWriteFile(Constants.NVMEM_WLAN_FW_SP_FILEID, firmware);

            if (result != 0) Debugger.Print("Firmware Update Failed!");

            Debugger.Print("******* FIRMWARE UPDATE COMPLETE *******");
            Debugger.Print("Rebooting with new firmware (fingers crossed)");

            // restart the cc3000 to allow the new firmware to take effect
            wifi.Start();  


            //// restore the mac address
            //var currentMacAddress = wifi.NonVolatileMemoryRead(Constants.NVMEM_MAC_FILEID, 0, 6);
            //if (!currentMacAddress.BytesEqual(savedMacAddress))
            //    result = wifi.NonVolatileMemoryWrite(Constants.NVMEM_MAC_FILEID, 0, savedMacAddress);
            //else
            //    result = 0;  // the Mac address was not changed, no need to rewrite it.  

            //if (result != 0) return false;  // failed to restore the Mac address!

            //// restore the radio stuff
            //var currentRadioStuff = wifi.NonVolatileMemoryRead(Constants.NVMEM_RM_FILEID, 0, 6);
            //if (!currentRadioStuff.BytesEqual(savedRadioStuff))
            //    result = wifi.NonVolatileMemoryWrite(Constants.NVMEM_RM_FILEID, 0, savedRadioStuff);
            //else
            //    result = 0;  // the Radio stuff was not changed, no need to rewrite it.  

            //if (result != 0) return false;  // failed to restore the Radio Stuff!

            return result == 0;
        }

        /// <summary>
        /// Device name (for Netbios and mDNS); Do not include the .local on the end.  Just letters and numbers.  Also, there is probably a character limit (probably 32), so keep it short.  
        /// </summary>
        public string Name
        {
            get
            {
                return (wifi == null) ? string.Empty : wifi.Name;
            }

            private set
            {
                if (value.IsNullOrWhitespace())
                {
                    wifi.Name = string.Empty;
                }
                else
                {
                    var name = value.ToLower().Trim();
                    wifi.Name = (name.IndexOf(".local") >= 0) ? name.Substring(0, name.Length - 6) : name;  // trim .local
                    //wifi.Name = (name.IndexOf(".local") >= 0) ? name : name + ".local";  // add .local
                }
                //_encodedName = NetBiosNaming.EncodeNetbiosName(value);
            }
        }

        /// <summary>
        /// The Globally Unique MAC address for this device
        /// </summary>
        public byte[] MacAddress
        {
            get
            {
                return wifi.MacAddress;
            }

            // TODO: implement the nvmem write command in the driver to set the Mac address (Dangerous?)
            //set
            //{
                
            //}

        }

        /// <summary>
        /// IP Address of this device.  To use a static address, just set this value to the desired IP address (also, set your subnet mask, gateway, and DNS Server)
        /// To go back to DHCP, just set this IP Address to null and attempt to connect
        /// When using DHCP (the default), once the connection succeeds, this IP Address will become populated with the assigned address
        /// </summary>
        public byte[] IPAddress
        {
            get
            {
                return wifi == null ? null : wifi.IPAddress;
            }

            set
            {
                wifi.IPAddress = value;
                wifi.useDHCP = value == null;
            }
        }

        /// <summary>
        /// Name of the Wifi Network you want to connect to
        /// </summary>
        public string SsidName 
        {
            get
            {
                return wifi == null ? null : wifi.ssid;
            }
        }

        /// <summary>
        /// Wifi Password
        /// </summary>
        public string Password { get; set; }

        /// <summary>
        /// Wifi Security Type
        /// </summary>
        public AccessPoint ConnectedNetwork { get; set; }


        /// <summary>
        /// IP Address of Primary Domain Name Server (DNS).  
        /// Note: this will be overwritten with one provided by the router if DHCP is enabled and router provides a DNS server.
        /// </summary>
        public byte[] DomainNameServer
        {
            get
            {
                return wifi == null ? null : wifi.DnsServer;
            }

            set
            {
                wifi.DnsServer = value;
            }
        }

        ///// <summary>
        ///// Secondary DNS server.  Never automatically assigned.  You have to set this value.  
        ///// It will be used if it is populated and the primary DNS server fails.  
        ///// </summary>
        //public byte[] DomainNameServer2 { get; set; }

        public byte[] Gateway
        {
            get
            {
                return wifi == null ? null : wifi.Gateway;
            }

            set
            {
                wifi.Gateway = value;
            }
        }

        public byte[] SubnetMask
        {
            get
            {
                return wifi == null ? null : wifi.SubnetMask;
            }

            set
            {
                wifi.SubnetMask = value;
            }
        }


        //internal static byte[] GatewayMac { get; set; }


        //public static uint LeaseRenewTime { get; internal set; }

        ///// <summary>
        ///// Interrupt Pin of SPI Bus
        ///// </summary>
        //internal Cpu.Pin IntPin { get; private set; }

        ///// <summary>
        ///// ChipSelect Pin of SPI Bus
        ///// </summary>
        //internal Cpu.Pin CSPin { get; private set; }

        ///// <summary>
        ///// Wifi Enable Pin
        ///// </summary>
        //internal Cpu.Pin EnablePin { get; private set; }

        ///// <summary>
        ///// The SPI Bus port
        ///// </summary>
        //internal SPI.SPI_module SpiPort { get; private set; }

        internal CC3000Driver wifi = null;
        //private string _name;
        //internal static byte[] _encodedName;

        public Socket OpenSocket(SocketType type = SocketType.DGRAM, Protocol protocol = Protocol.UDP, AddressFamily family = AddressFamily.INET)
        {
            var s = new Socket(wifi, (int)family, (int)type, (int)protocol);

            return s;
        }

        public TcpSocket OpenTcpSocket()
        {
            return new TcpSocket(wifi);
        }

        public UdpSocket OpenUdpSocket()
        {
            return new UdpSocket(wifi);
        }

        /// <summary>
        /// Connect directly to the desired WiFi AccessPoint right now.  This completely bypasses the system of saved Profiles.  
        /// And if the cc3000 needs to reconnect later, that may fail because it must use a stored profile.  
        /// </summary>
        /// <param name="ap"></param>
        /// <returns></returns>
        public bool DirectConnect(AccessPoint ap = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            return wifi.Connect(ap);
        }

        /// <summary>
        /// Connect using Stored Profiles
        /// </summary>
        /// <param name="ap"></param>
        /// <returns></returns>
        public void Connect(AccessPoint ap = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            if (ap != null)
            {
                wifi.DeleteAllProfiles();
                wifi.AddProfile(ap.Ssid, ap.Security, ap.Key);
            }

            // Connect using Profiles
            wifi.SetConnectionPolicy(false, false, true);
        }

        public void ActivateSmartConfig(int timeout = 7, bool useEncryption = false, string encryptionKey = "smartconfigAES16")
        {
            //wifi.IsResetting = true; // This prevents the watchdog from restarting during SmartConfig
            wifi.DisableWatchDog();

            wifi.SetConnectionPolicy(false, false, false);

            wifi.DeleteAllProfiles();

            if (wifi.IsConnected) wifi.Disconnect();

            while (wifi.IsConnected) Thread.Sleep(100);

            wifi.SetSmartConfigPrefix();

            wifi.StartSmartConfig(timeout, encryptionKey);

            Debugger.Print("Magic Packet Received!!!");

            if (useEncryption)
            {
                // create new entry for AES encryption key and save the key
                wifi.CreateEntry(Constants.NVMEM_AES128_KEY_FILEID, 16);

                var key = new byte[16];
                key.Overwrite(0, Encoding.UTF8.GetBytes(encryptionKey), 16);

                wifi.NvMemWriteFile(Constants.NVMEM_AES128_KEY_FILEID, key);

                wifi.ProcessSmartConfig(encryptionKey);
            }
            else
            {
                wifi.ProcessSmartConfig(null);
            }

            wifi.SetConnectionPolicy(false, false, true);

            System.Threading.Thread.Sleep(2000);  // it really seems to need this 2 seconds to fully save the policy change...  (1 second was not working!)

            wifi.Shutdown();

            System.Threading.Thread.Sleep(1000);

            wifi.Start();

            //wifi.IsResetting = false;
        }

        /// <summary>
        /// Scan for available Wifi networks and return them as an array
        /// This is a Synchronous call and will block execution
        /// </summary>
        public AccessPointResult[] NetworkScan(int Max = 5)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            return wifi.NetworkScan(Max);
        }

        //public static void Reset()
        //{
        //    nic.Restart();
        //}

        private TimeSpan LastInternetCheck = TimeSpan.MinValue;
        private bool InternetUp = false;
        private bool IsLocalNameServicesStarted = false;

        /// <summary>
        /// True means you are connected to the Internet.  False means you are not able to reach computers on the Internet, although local computers may be reachable
        /// Note: this call is expensive and synchronous, so don't call it a lot!  Use it Judiciously!  
        /// Also, if you call this more frequently than every 5 seconds, you will get the last cached value if wifi is connected!
        /// </summary>
        public bool ConnectedToInternet
        {
            get
            {
                if (!IsConnectedToWifi) return false;

                if (LastInternetCheck > TimeSpan.MinValue && LastInternetCheck > Microsoft.SPOT.Hardware.PowerState.Uptime.Subtract(new TimeSpan(0, 0, 5)))
                {
                    // This prevents hitting the server more than once every 5 seconds.  So someone could put the ConnectedToInternet Property in a loop and it would
                    // not try to hammer the server and potentially get blocked by the server for a DoS attack.  
                    //Debug.WriteLine("Responding with cached value.");

                    return InternetUp;
                }

                LastInternetCheck = Microsoft.SPOT.Hardware.PowerState.Uptime;

                //TODO: implement the http request below
                
                //HttpResponse response = null;

                //try
                //{
                //    var r = new HttpRequest("http://www.msftncsi.com/ncsi.txt");
                //    r.Headers.Add("Accept", "*/*");  // Add custom properties to the Request Header
                //    response = r.Send();
                //}
                //catch { }  // ignore exceptions!  eek!

                //InternetUp = response != null;

                return InternetUp;

                //Debug.WriteLine("Response: " + response.Message);

                // ping something on the internet, or hit msftncsi.com just like Windows PCs do...
                // Great info here: http://blog.superuser.com/2011/05/16/windows-7-network-awareness/
                // Also, since the call can't block, we'll have to hit the server every 10 seconds or so... eek! 
            }
        }

        /// <summary>
        /// True means that this adapter is connected to the Wifi Access Point
        /// </summary>
        public bool IsConnectedToWifi
        {
            get
            {
                try
                {
                    if (wifi == null) return false;
                    return wifi.IsConnected;
                }
                catch
                {
                    return false;
                }
            }
        }


        public void Stop()
        {
            //if (responderThread == null) return;
            Debugger.Print("Stopping Network");

            try
            {
                //PollingTimer.Change(Timeout.Infinite, Timeout.Infinite);
                this.IPAddress = null;

                wifi.Disconnect();

                //nic.OnLinkChangedEvent = null;
                //nic.OnFrameArrived = null;

                //responderThread.Abort();
            }
            catch { }
        }


        /// <summary>
        /// Get host IP by name. Obtain the IP Address of machine on network by its name
        /// </summary>
        /// <param name="p"></param>
        /// <returns>IP Address</returns>
        public byte[] LookupAddress(string p)
        {
            if (wifi == null) return null;

            return wifi.LookupAddress(p);
        }

        public int Ping(object googleIp)
        {
            throw new NotImplementedException();
        }

        //public void SetLocalName()
        //{
        //    throw new NotImplementedException();
        //}

        public void SendDeviceAdvertisement(string name = null)
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            bool closeWhenFinished = false;

            if (mdns == null)
            {
                mdns = this.OpenUdpSocket();
                closeWhenFinished = true;
            }

            //byte[] mdnsResponse = Networking.DNS.ConstructDnsResponse(wifi.Name, new byte[2] { message[0], message[1] }, wifi.IPAddress);
            byte[] mdnsResponse = Networking.DNS.ConstructMDnsResponse((name == null ? (this.Name + ".local") : name), this.IPAddress, 200);

            Debug.Print("Sending MDNS Response!");
            mdns.Send(mdnsResponse, new byte[] { 224, 0, 0, 251 }, 5353);

            if (closeWhenFinished) mdns.Close();
        }

        public void ResetSockets()
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            wifi.ResetAndReconnect(true, false);
        }

        public void Reset()
        {
            if (wifi == null) throw new Exception("Start the Wifi Adapter first");

            wifi.ResetAndReconnect();
        }

        public bool IsResetting
        {
            get { return wifi.IsResetting; }
        }

        public void Dispose()
        {
            Debugger.VerbosePrint("Disposing of the cc3000");

            this.Stop();  // disconnect from any connected access points
            wifi.Shutdown();  // turn off the cc3000 chip
            wifi.Dispose();
            wifi = null;  // dispose of the wifi class
        }

        ~WifiAdapter()
        {
	        Debugger.Print("Destructor");
        }
    }

    /// <summary>
    /// Profiles for how the Processor/board is connected to the Networking Controller
    /// </summary>
    public enum InterfaceProfile
    {
        /// <summary>
        /// A CC3000 connected to Netduino2Plus on MOSI(D11), SCK(D13), MISO(D12), INT(D6), PWREN(D5), CS(D4)
        /// </summary>
        NetduinoPlus2_CC3000,
        /// <summary>
        /// A generic CC3000 connected to MOSI(PB5), SCK(PB3), MISO(PB4), INT(PA14), CS(PA13)
        /// </summary>
        Generic_CC3000,
        /// <summary>
        /// A Gadgeteer CC3000 module connected to Netduino Go on Socket 3
        /// </summary>
        NetduinoGo_Socket3_CC3000,
        /// <summary>
        /// A Gadgeteer CC3000 module connected to CerbuinoBee on Socket 1
        /// </summary>
        CerbuinoBee_Socket1_CC3000,
        /// <summary>
        /// A Gadgeteer CC3000 module connected to CerbuinoBee on Socket 6
        /// </summary>
        Cerberus_Socket6_CC3000,
        CerbuinoBee_Adhoc_CC3000,
        VTech_Device,
        VTech_Device2

    };

}